package com.bensonbasement.snorflux.client;

import java.net.SocketAddress;

import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import com.bensonbasement.snorflux.messages.fromclient.IFromClientMessage;
import com.bensonbasement.snorflux.messages.fromclient.JoinMessage;
import com.bensonbasement.snorflux.messages.fromserver.JoinedMessage;
import com.bensonbasement.snorflux.messages.fromserver.ServerTimeMessage;
import com.bensonbasement.snorflux.messages.fromserver.WorldTimeMessage;

public class SnorfluxClientHandler extends IoHandlerAdapter {
	public interface Callback {
		void handleConnected(JoinedMessage message);
		void handleDisconnected();
		void handleServerTimeMessage(ServerTimeMessage message);
		void handleWorldTimeMessage(WorldTimeMessage message);
		void invokeLater(Runnable runnable);
	}
	
	private IoConnector connector;
	private IoSession session;
	private boolean joined;
	private Callback callback;
	private String playerName = null; // Will be set by connect method.
	
	public SnorfluxClientHandler(Callback callback) {
		connector = new NioSocketConnector();
		connector.getFilterChain().addLast("codec",new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
		connector.setHandler(this);
		session = null;
		joined = false;
		this.callback = callback;
	}
	
	public boolean isConnected() {
		return (this.session != null);
	}
	
	public boolean isJoined() {
		return (this.session != null && joined);
	}
	
	public void connect(SocketAddress address,String playerName) {
		connector.connect(address);
		this.playerName = playerName;
	}
	
	public void disconnect() {
		if (this.session != null) {
			session.close(false);
		}
	}
	
	public void sendMessage(IFromClientMessage message) {
		if (session != null && joined) {
			session.write(message);
		}
		else {
			throw new RuntimeException("Can't call sendMessage when not joined!");
		}
	}
	
	@Override
	public void sessionOpened(final IoSession session) {
		callback.invokeLater(new Runnable() {
			@Override
			public void run() {
				SnorfluxClientHandler.this.session = session;
				// Request to join the game.
				JoinMessage message = new JoinMessage(SnorfluxClientHandler.this.playerName);
				session.write(message);
				System.out.println("Connected to server.");
			}
		});
	}
	
	@Override
	public void sessionClosed(IoSession session) {
		callback.invokeLater(new Runnable() {
			@Override
			public void run() {
				SnorfluxClientHandler.this.session = null;
				SnorfluxClientHandler.this.joined = false;
				SnorfluxClientHandler.this.playerName = null;
				System.out.println("No longer connected to server.");
				SnorfluxClientHandler.this.callback.handleDisconnected();
			}
		});

	}
	
	@Override
	public void exceptionCaught(IoSession session, Throwable cause) {
		cause.printStackTrace();
		session.close(true);
	}
	
	@Override
	public void messageReceived(final IoSession session, final Object message) {
		callback.invokeLater(new Runnable() {
			@Override
			public void run() {
				if (message instanceof JoinedMessage) {
					handleJoinedMessage((JoinedMessage) message);
				}
				else if (message instanceof WorldTimeMessage) {
					callback.handleWorldTimeMessage((WorldTimeMessage)message);
				}
				else if (message instanceof ServerTimeMessage) {
					callback.handleServerTimeMessage((ServerTimeMessage)message);
				}
				else {
					throw new RuntimeException("Unrecognized message type.");
				}
			}
		});
	}
	
	private void handleJoinedMessage(JoinedMessage message) {
		this.playerName = message.getPlayerName();
		this.joined = true;
		this.callback.handleConnected(message);
	}
}
